﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using agree.schema;

using alib;
using alib.Debugging;
using alib.Enumerable;

namespace agree
{
	public interface IMutExcGroupDescriptor
	{
		object GroupByFunc(IDiscriminant disc);

		IDiscriminant Parent(IDiscriminant disc);

		bool InitialFilter(IDiscriminant disc);

		IMutualExclusionGroup MakeGroup(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant Parent);
	}

	public interface IMutualExclusionGroup
	{
		IEnumerable<IEnumerable<IDiscriminant>> Options { get; }
		String MasterFormat();
		String FormatOneDiscriminant(IDiscriminant disc);
	}

	public class ConstituentLabelMEGDesc : IMutExcGroupDescriptor
	{
		public object GroupByFunc(IDiscriminant disc)
		{
			return ((ConstituentLabelDiscriminant)disc).Span;
		}

		public IDiscriminant Parent(IDiscriminant disc)
		{
			return ((ConstituentLabelDiscriminant)disc).GetDependencyTemplates().First();
		}

		public bool InitialFilter(IDiscriminant disc)
		{
			return disc is ConstituentLabelDiscriminant;
		}

		public IMutualExclusionGroup MakeGroup(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			return new ConsLblMEG(discs, parent);
		}
	}



	public class ConsLblMEG : IMutualExclusionGroup
	{
		public ConsLblMEG(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			options = discs;
			formatted = String.Format("\t\"{0}\" is a(n)", ((ConstituentDiscriminant)parent).Span.Surface);
		}

		IEnumerable<IEnumerable<IDiscriminant>> options;
		//ConstituentDiscriminant parent;
		String formatted;

		public IEnumerable<IEnumerable<IDiscriminant>> Options
		{
			get { return options; }
		}

		public String MasterFormat()
		{
			return formatted;
		}

		public String FormatOneDiscriminant(IDiscriminant disc)
		{
			return ((ConstituentLabelDiscriminant)disc).Label;
		}
	}

	public class ConstituentPredMEGDesc : IMutExcGroupDescriptor
	{
		public object GroupByFunc(IDiscriminant disc)
		{
			return ((PredDiscriminant)disc).Span;
		}

		public IDiscriminant Parent(IDiscriminant disc)
		{
			return ((PredDiscriminant)disc).GetDependencyTemplates().First();
		}

		public bool InitialFilter(IDiscriminant disc)
		{
			return disc is PredDiscriminant;
		}

		public IMutualExclusionGroup MakeGroup(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			return new ConsPredMEG(discs, parent);
		}
	}

	public class ConsPredMEG : IMutualExclusionGroup
	{
		public ConsPredMEG(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			options = discs;
			formatted = String.Format("\t\"{0}\" has pred:  ", ((ConstituentDiscriminant)parent).Span.Surface);
		}

		IEnumerable<IEnumerable<IDiscriminant>> options;
		//ConstituentDiscriminant parent;
		String formatted;

		public IEnumerable<IEnumerable<IDiscriminant>> Options
		{
			get { return options; }
		}

		public String MasterFormat()
		{
			return formatted;
		}

		public String FormatOneDiscriminant(IDiscriminant disc)
		{
			return ((PredDiscriminant)disc).PS.Pred;
		}
	}

	public class RuleSpanMEGDesc : IMutExcGroupDescriptor
	{
		public object GroupByFunc(IDiscriminant disc)
		{
			return ((RuleSpanDiscriminant)disc).Span;
		}

		public IDiscriminant Parent(IDiscriminant disc)
		{
			return ((RuleSpanDiscriminant)disc).GetDependencyTemplates().First();
		}

		public bool InitialFilter(IDiscriminant disc)
		{
			return disc is RuleSpanDiscriminant;
		}

		public IMutualExclusionGroup MakeGroup(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			return new RuleSpanMEG(discs, parent);
		}
	}

	public class RuleSpanMEG : IMutualExclusionGroup
	{
		public RuleSpanMEG(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			options = discs;
			formatted = String.Format("\t\"{0}\" uses rule:  ", ((ConstituentDiscriminant)parent).Span.Surface);
		}

		IEnumerable<IEnumerable<IDiscriminant>> options;
		//ConstituentDiscriminant parent;
		String formatted;

		public IEnumerable<IEnumerable<IDiscriminant>> Options
		{
			get { return options; }
		}

		public String MasterFormat()
		{
			return formatted;
		}

		public String FormatOneDiscriminant(IDiscriminant disc)
		{
			return ((RuleSpanDiscriminant)disc).Rule;
		}
	}

	public class EPLinkTargetMEGDesc : IMutExcGroupDescriptor
	{
		struct grouping_struct
		{
			public TokenSpan ts;
			public String arg;

			public override bool Equals(object obj)
			{
				if (!(obj is grouping_struct))
					return false;

				grouping_struct gs = (grouping_struct)obj;

				return arg.Equals(gs.arg) &&
					((ts != null && ts.Equals(gs.ts)) || ts == gs.ts);
			}

			public override int GetHashCode()
			{

				return ts != null ? ts.GetHashCode() ^ arg.GetHashCode() : arg.GetHashCode();
			}
		}

		public object GroupByFunc(IDiscriminant disc)
		{
			var epsld = (EPSpanLinkDiscriminant)disc;
			return new grouping_struct { ts = epsld.Span, arg = epsld.Role };
		}

		public IDiscriminant Parent(IDiscriminant disc)
		{
			return ((EPSpanLinkDiscriminant)disc).GetDependencyTemplates().First();
		}

		public bool InitialFilter(IDiscriminant disc)
		{
			return disc is EPSpanLinkDiscriminant;
		}

		public IMutualExclusionGroup MakeGroup(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			return new EPLinkTargetMEG(discs, parent);
		}
	}

	public class EPLinkTargetMEG : IMutualExclusionGroup
	{
		public EPLinkTargetMEG(IEnumerable<IEnumerable<IDiscriminant>> discs, IDiscriminant parent)
		{
			options = discs;
			var epsld = (EPSpanLinkDiscriminant)discs.First().First();
			formatted = String.Format("\t\"{0}\", role {1} =  ", epsld.Span.Surface, epsld.Role);
		}

		IEnumerable<IEnumerable<IDiscriminant>> options;
		//ConstituentDiscriminant parent;
		String formatted;

		public IEnumerable<IEnumerable<IDiscriminant>> Options
		{
			get { return options; }
		}

		public String MasterFormat()
		{
			return formatted;
		}

		public String FormatOneDiscriminant(IDiscriminant disc)
		{
			return ((EPSpanLinkDiscriminant)disc).TargetSpan.Surface;
		}
	}

	public class DiscriminantSet : identity_base
	{
		public Dictionary<IDiscriminant, HashSet<IDerivation>> disc_to_der = new Dictionary<IDiscriminant, HashSet<IDerivation>>();
		public Dictionary<IDerivation, HashSet<IDiscriminant>> der_to_disc = new Dictionary<IDerivation, HashSet<IDiscriminant>>();

		public List<HashSet<IDiscriminant>> equiv_sets = new List<HashSet<IDiscriminant>>();
		public Dictionary<IDiscriminant, HashSet<IDiscriminant>> disc_to_equiv_set = new Dictionary<IDiscriminant, HashSet<IDiscriminant>>();

		public Dictionary<IDerivation, Dictionary<RuleSpanDiscriminant, IDerivation>> rs_node_dict = new Dictionary<IDerivation, Dictionary<RuleSpanDiscriminant, IDerivation>>();

		public T GetCachedCopy<T>(T d) where T : IDiscriminant
		{
			if (disc_lookup.ContainsKey(d.ToString()))
			{
				return (T)disc_lookup[d.ToString()];
			}
			else
			{
				disc_lookup[d.ToString()] = d;
				return d;
			}
		}

		Dictionary<string, IDiscriminant> disc_lookup = new Dictionary<string, IDiscriminant>();

		NodeLabeler nl = null;

		public DiscriminantSet(IRtParent rts, semi.SemI semI, IEnumerable<IDerivation> derivs, ParseControl ctrl)
			: base(semI)
		{
			TokenizedString ts = ctrl.FindTrace<TokenizedString>();

			foreach (var rd in derivs)
			{
				der_to_disc[rd] = new HashSet<IDiscriminant>();

				agree.mrs.Mrs xmrs = rd.DerivationTfs().Mrs();

				DependencyMrs dmrs = new DependencyMrs(xmrs);

				PredSpan[] pred_spans = new PredSpan[dmrs.preds.Length];

				for (int m = 0; m < dmrs.preds.Length; m++)
				{
#if true
					throw not.impl;
#else
					ChartSpan cs = default(ChartSpan);/// dmrs.token_spans[m];
					ChartSpan char_span = ts.MinimalSpanToCharacterSpan(cs);
					String txt = ts.Text.Substring(char_span.StartIndex, char_span.EndIndex - char_span.StartIndex + 1);
					TokenSpan tok_span = new TokenSpan(cs.StartIndex, cs.EndIndex, txt);
					PredSpan ps = new PredSpan(dmrs.preds[m], tok_span);
					pred_spans[m] = ps;

					AssociateDiscriminant(new PredDiscriminant(ps), rd);
#endif
				}

				foreach (var link in dmrs.links.SelectMany())
				{
					if (dmrs.is_quantifier[link.from])
						continue;

					if (link.link_type != DependencyMrs.DMRSLinkType.NONE)
					{
						if (link.to == -1)
						{
							//AssociateDiscriminant(new PredRolePredDiscriminant(pred_spans[link.from], link.idx_arg, PredSpan.NonePredSpan), rd);
							AssociateDiscriminant(new EPSpanLinkDiscriminant(pred_spans[link.from].Span, link.idx_arg, TokenSpan.NoneTokenSpan), rd);
						}
						else
						{
							//AssociateDiscriminant(new PredRolePredDiscriminant(pred_spans[link.from], link.idx_arg, pred_spans[link.to]), rd);
							AssociateDiscriminant(new EPSpanLinkDiscriminant(pred_spans[link.from].Span, link.idx_arg, pred_spans[link.to].Span), rd);
						}
					}

					else if (link.lbl_link_type != DependencyMrs.DMRSLinkType.NONE)
					{
						if (link.to == -1)
						{
							//AssociateDiscriminant(new PredRolePredDiscriminant(pred_spans[link.from], link.lbl_arg, PredSpan.NonePredSpan), rd);
							AssociateDiscriminant(new EPSpanLinkDiscriminant(pred_spans[link.from].Span, link.lbl_arg, TokenSpan.NoneTokenSpan), rd);

						}
						else
						{
							AssociateDiscriminant(new EPSpanLinkDiscriminant(pred_spans[link.from].Span, link.lbl_arg, pred_spans[link.to].Span), rd);
						}
					}
				}

				int start = 0;
				rs_node_dict[rd] = new Dictionary<RuleSpanDiscriminant, IDerivation>();

				ExtractTokenSpanDiscriminants(rd, rd, ref start, rs_node_dict[rd]);
			}

			SetupEquivGroups();
			//FindMutuallyExclusiveGroups(new ConstituentLabelMEGDesc());
		}



		int ExtractTokenSpanDiscriminants(IDerivation dv, IDerivation dv_top, ref int start,
			Dictionary<RuleSpanDiscriminant, IDerivation> rs_node_dict)
		{
			int my_start = start;
			string rule = dv.ChartLicense.Name;
			int end = default(int);

			if (dv.Daughters.Count == 0)
			{
				start += ((LexicalEntry)dv.ChartLicense).words.Length;
				end = start - 1;
			}
			else
			{
				for (int j = 0; j < dv.Daughters.Count; j++)
					end = ExtractTokenSpanDiscriminants(dv.Daughters[j], dv_top, ref start, rs_node_dict);
			}

			ITokenizer tok = (ITokenizer)P.Tokenizer.FindTracePropertyValue(dv);
			String surface = tok.MakeSurface(dv.ReadBack());

			TokenSpan span = new TokenSpan(my_start, end, surface);

			ConstituentDiscriminant cd = GetCachedCopy(new ConstituentDiscriminant(span));
			AssociateDiscriminant(cd, dv_top);

			RuleSpanDiscriminant rsd = GetCachedCopy(new RuleSpanDiscriminant(span, rule));
			AssociateDiscriminant(rsd, dv_top);
			rs_node_dict[rsd] = dv;

			string node_label = nl.FindLabel(dv.DerivationTfs());
			ConstituentLabelDiscriminant cld = GetCachedCopy(new ConstituentLabelDiscriminant(span, node_label));
			AssociateDiscriminant(cld, dv_top);

			return end;
		}

		public void SetupEquivGroups()
		{
			IDiscriminant[] keys = disc_to_der.Keys.ToArray();
			bool[] skip_me = new bool[keys.Length];

			List<HashSet<IDiscriminant>> disc_sets = new List<HashSet<IDiscriminant>>();

			for (int i = 0; i < keys.Length; i++)
			{
				if (skip_me[i])
					continue;

				HashSet<IDiscriminant> cur_set = new HashSet<IDiscriminant>();

				cur_set.Add(keys[i]);

				var dersi = disc_to_der[keys[i]];

				for (int j = i + 1; j < keys.Length; j++)
				{
					if (skip_me[j])
						continue;

					HashSet<IDerivation> dersj = disc_to_der[keys[j]];

					//if (dersi.Count == dersj.Count && dersi.All(x => dersj.Contains(x))dersi.Count == dersi.Intersect(dersj).ToArray().Length)
					if (dersi.Count == dersj.Count && dersi.All(x => dersj.Contains(x)))
					{
						skip_me[j] = true;
						cur_set.Add(keys[j]);
					}
				}

				equiv_sets.Add(cur_set);

				foreach (var disc in cur_set)
				{
					disc_to_equiv_set[disc] = cur_set;
				}
			}
		}

		public Dictionary<IDiscriminant, List<IMutualExclusionGroup>> FindMutualExclusionGroups(IEnumerable<IMutExcGroupDescriptor> megds)
		{
			//var candidates = disc_to_der.Keys.Where(x => megd.InitialFilter(x)).GroupBy(x => GetCachedCopy(x.GetDependencyTemplates().First()));

			Dictionary<IDiscriminant, List<IMutualExclusionGroup>> results = new Dictionary<IDiscriminant, List<IMutualExclusionGroup>>();

			foreach (var megd in megds)
			{
				var candidates = disc_to_der.Keys.Where(x => megd.InitialFilter(x)).GroupBy(x => megd.GroupByFunc(x));

				var confirmed_groups = new List<Tuple<IDiscriminant, List<List<IDiscriminant>>>>();

				foreach (var group in candidates)
				{
					var equiv_groups = group.GroupBy(x => this.disc_to_equiv_set[x]);

					IDiscriminant parent = megd.Parent(group.First());
					if (parent != null)
						parent = GetCachedCopy(parent);

					int target_count = der_to_disc.Count;
					if (parent != null)
						target_count = disc_to_der[parent].Count;

					var items = equiv_groups.OrderBy(x => disc_to_der[x.First()].Count).ToArray();
					int total_left = items.Select(x => disc_to_der[x.First()].Count).Sum();

					var hypotheses = new List<Tuple<List<List<IDiscriminant>>, HashSet<IDerivation>>>();

					var hs_par = parent as IHasSpanDiscriminant;

					for (int i = 0; i < items.Length; i++)
					{
						var cur_ders = disc_to_der[items[i].First()];

						if (cur_ders.Count == target_count)
							continue;

						total_left -= cur_ders.Count;

						int curCount = hypotheses.Count;
						for (int hyp_idx = 0; hyp_idx < curCount; hyp_idx++)
						{
							var tup = hypotheses[hyp_idx];
							if (cur_ders.Count + tup.Item2.Count <= target_count)
							{
								var union = new HashSet<IDerivation>(tup.Item2.Union(cur_ders));

								var new_tup = new Tuple<List<List<IDiscriminant>>, HashSet<IDerivation>>
										(new List<List<IDiscriminant>>(tup.Item1), union);
								new_tup.Item1.Add(new List<IDiscriminant>(items[i]));

								//if they have empty intersection the union count will be sum of part-counts
								if (union.Count == cur_ders.Count + tup.Item2.Count)
								{
									if (union.Count == target_count)
									{
										if (!results.ContainsKey(parent))
											results[parent] = new List<IMutualExclusionGroup>();

										results[parent].Add(megd.MakeGroup(new_tup.Item1, parent));
										//confirmed_groups.Add(new Tuple<IDiscriminant, List<List<IDiscriminant>>>(group.Key, new_tup.Item1));
									}
									else
									{
										hypotheses.Add(new_tup);
									}
								}
							}
						}

						if (i < items.Length - 1)
						{
							if (cur_ders.Count + total_left >= target_count)
							{
								hypotheses.Add(new Tuple<List<List<IDiscriminant>>, HashSet<IDerivation>>(new List<List<IDiscriminant>> { new List<IDiscriminant>(items[i]) }, cur_ders));
							}

							hypotheses.RemoveAll(x => x.Item2.Count + total_left < target_count);
						}
					}
				}
			}
			return results;
		}

		protected void AssociateDiscriminant(IDiscriminant disc, IDerivation der)
		{
			IDiscriminant d = GetCachedCopy(disc);

			der_to_disc[der].Add(d);
			if (!disc_to_der.ContainsKey(d))
				disc_to_der[d] = new HashSet<IDerivation>();
			disc_to_der[d].Add(der);

			IDependentDiscriminant dd = d as IDependentDiscriminant;

			if (dd != null && dd.GetDependencies().Count == 0)
			{
				foreach (var dep in dd.GetDependencyTemplates())
				{
					IDiscriminant cached_dep = GetCachedCopy(dep);
					cached_dep.AddDependant(dd);
					dd.AddDependency(cached_dep);
				}
			}
		}
	};

}
